<html><head><meta charset="utf-8"><title>19 LinkedBlockingQueue 源码解析-慕课专栏</title>
			<meta http-equiv="X-UA-Compatible" content="IE=edge, chrome=1">
			<meta name="renderer" content="webkit">
			<meta property="qc:admins" content="77103107776157736375">
			<meta property="wb:webmaster" content="c4f857219bfae3cb">
			<meta http-equiv="Access-Control-Allow-Origin" content="*">
			<meta http-equiv="Cache-Control" content="no-transform ">
			<meta http-equiv="Cache-Control" content="no-siteapp">
			<link rel="apple-touch-icon" sizes="76x76" href="https://www.imooc.com/static/img/common/touch-icon-ipad.png">
			<link rel="apple-touch-icon" sizes="120x120" href="https://www.imooc.com/static/img/common/touch-icon-iphone-retina.png">
			<link rel="apple-touch-icon" sizes="152x152" href="https://www.imooc.com/static/img/common/touch-icon-ipad-retina.png">
			<link href="https://moco.imooc.com/captcha/style/captcha.min.css" rel="stylesheet">
			<link rel="stylesheet" href="https://www.imooc.com/static/moco/v1.0/dist/css/moco.min.css?t=201907021539" type="text/css">
			<link rel="stylesheet" href="https://www.imooc.com/static/lib/swiper/swiper-3.4.2.min.css?t=201907021539">
			<link rel="stylesheet" href="https://static.mukewang.com/static/css/??base.css,common/common-less.css?t=2.5,column/zhuanlanChapter-less.css?t=2.5,course/inc/course_tipoff-less.css?t=2.5?v=201907051055" type="text/css">
			<link charset="utf-8" rel="stylesheet" href="https://www.imooc.com/static/lib/ueditor/themes/imooc/css/ueditor.css?v=201907021539"><link rel="stylesheet" href="https://www.imooc.com/static/lib/baiduShare/api/css/share_style0_16.css?v=6aba13f0.css"></head>
			<body><div id="main">


<div class="main-con hide-menu">
    <!-- 左侧菜单 & 索引 -->
    
    <div class="right-content" style="padding-left: 0px;">
        <div class="container clearfix" id="top" style="width: 1134px; display: block;">
            
            
            <div class="center_con js-center_con l" style="width: 1134px;">
                <div class="article-con">
                                            <!-- 买过的阅读 -->
                        

                    
                    <div class="art-title" style="margin-top: 0px;">
                        19 LinkedBlockingQueue 源码解析
                    </div>
                    <div class="art-info clearfix">
                        
                        <span class="l">
                            更新时间：2019-10-11 17:02:21
                        </span>
                    </div>
                    <div class="art-top">
                                                <img src="https://img1.mukewang.com/5d91984400013fbc06400359.jpg" alt="">
                                                                        <div class="famous-word-box">
                            <img src="https://www.imooc.com/static/img/column/bg-l.png" alt="" class="bg1 bg">
                            <img src="https://www.imooc.com/static/img/column/bg-r.png" alt="" class="bg2 bg">
                            <div class="famous-word">从不浪费时间的人，没有工夫抱怨时间不够。<p class="author">——杰弗逊</p></div>
                        </div>
                                            </div>
                    <div class="art-content js-lookimg">
                        <div id="article_content">
                            <div class="cl-preview-section"><h2 id="引导语" style="font-size: 30px;">引导语</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">说到队列，大家的反应可能是我从来都没有用过，应该是不重要的 API 吧。如果这么想，那就大错特错了，我们平时使用到的线程池、读写锁、消息队列等等技术和框架，底层原理都是队列，所以我们万万不可轻视队列，队列是很多高级 API 的基础，学好队列，对自己深入 Java 学习非常重要。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本文主要以 LinkedBlockingQueue 队列为例，详细描述一下底层具体的实现。</p>
</div><div class="cl-preview-section"><h2 id="整体架构" style="font-size: 30px;">1 整体架构</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">LinkedBlockingQueue 中文叫做链表阻塞队列，这个命名很好，从命名上就知道其底层数据结构是链表，并且队列是可阻塞的。接下来，我们就从整体结构上看看 LinkedBlockingQueue。</p>
</div><div class="cl-preview-section"><h3 id="类图">1.1 类图</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们来看下 LinkedBlockingQueue 类图，如下：<br>
<img class="" src="https://img.mukewang.com/5d9db50c00011ed210740736.png" data-original="//img.mukewang.com/5d9db50c00011ed210740736.png" alt="图片描述">从类图中，我们大概可以看出两条路径：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">AbstractQueue -&gt; AbstractCollection -&gt; Collection -&gt;Iterable 这条路径依赖，主要是想复用  Collection 和 迭代器的一些操作，这些我们在说集合的时候，都知道这些类是干什么，能干什么，就不细说了；</li>
<li style="font-size: 20px; line-height: 38px;">BlockingQueue -&gt; Queue -&gt; Collection，BlockingQueue 和 Queue 是新出来的两个接口，我们重点说一下。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">Queue 是最基础的接口，几乎所有的队列实现类都会实现这个接口，该接口定义出了队列的三大类操作：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">新增操作：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">add 队列满的时候抛出异常；</li>
<li style="font-size: 20px; line-height: 38px;">offer 队列满的时候返回 false。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">查看并删除操作：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">remove 队列空的时候抛异常；</li>
<li style="font-size: 20px; line-height: 38px;">poll 队列空的时候返回 null。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">只查看不删除操作：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">element 队列空的时候抛异常；</li>
<li style="font-size: 20px; line-height: 38px;">peek 队列空的时候返回 null。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">一共 6 种方法，除了以上分类方法，也可以分成两类：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">遇到队列满或空的时候，抛异常，如 add、remove、element；</li>
<li style="font-size: 20px; line-height: 38px;">遇到队列满或空的时候，返回特殊值，如 offer、poll、peek。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">实际上，这些都比较难记忆。每次需要使用的时候，我都会看会源码，才能想起这个方法是抛异常还是返回特殊值。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">BlockingQueue 在 Queue 的基础上加上了阻塞的概念，比如一直阻塞，还是阻塞一段时间。为了方便记忆，我们画一个表格，如下：</p>
</div><div class="cl-preview-section"><div class="table-wrapper"><table>
<thead>
<tr>
<th align="center"></th>
<th align="center">抛异常</th>
<th align="center">特殊值</th>
<th align="center">一直阻塞</th>
<th align="center">阻塞一段时间</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">新增操作–队列满</td>
<td align="center"> add</td>
<td align="center">offer 返回 false</td>
<td align="center">put</td>
<td align="center">offer 过超时时间返回 false</td>
</tr>
<tr>
<td align="center"> 查看并删除操作–队列空</td>
<td align="center"> remove</td>
<td align="center">poll 返回 null</td>
<td align="center">take</td>
<td align="center">poll 过超时时间返回 null</td>
</tr>
<tr>
<td align="center"> 只查看不删除操作–队列空</td>
<td align="center"> element</td>
<td align="center">peek 返回 null</td>
<td align="center"> 暂无</td>
<td align="center">暂无</td>
</tr>
</tbody>
</table>
</div></div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">PS: remove 方法，BlockingQueue 类注释中定义的是抛异常，但 LinkedBlockingQueue 中 remove 方法实际是返回 false。<br>
从表格中可以看到，在新增和查看并删除两大类操作上，BlockingQueue 增加了阻塞的功能，而且可以选择一直阻塞，或者阻塞一段时间后，返回特殊值。</p>
</div><div class="cl-preview-section"><h3 id="类注释">1.2 类注释</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">我们看看从 LinkedBlockingQueue 的类注释中能得到那些信息：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">基于链表的阻塞队列，其底层的数据结构是链表；</li>
<li style="font-size: 20px; line-height: 38px;">链表维护先入先出队列，新元素被放在队尾，获取元素从队头部拿；</li>
<li style="font-size: 20px; line-height: 38px;">链表大小在初始化的时候可以设置，默认是 Integer 的最大值；</li>
<li style="font-size: 20px; line-height: 38px;">可以使用 Collection 和 Iterator 两个接口的所有操作，因为实现了两者的接口。</li>
</ol>
</div><div class="cl-preview-section"><h3 id="内部构成">1.3 内部构成</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">LinkedBlockingQueue 内部构成简单来说，分成三个部分：链表存储 + 锁 + 迭代器，我们来看下源码。</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 链表结构 begin</span>
<span class="token comment">//链表的元素</span>
<span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
    E item<span class="token punctuation">;</span>

    <span class="token comment">//当前元素的下一个，为空表示当前节点是最后一个</span>
    Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> next<span class="token punctuation">;</span>

    <span class="token function">Node</span><span class="token punctuation">(</span>E x<span class="token punctuation">)</span> <span class="token punctuation">{</span> item <span class="token operator">=</span> x<span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span>

<span class="token comment">//链表的容量，默认 Integer.MAX_VALUE</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> <span class="token keyword">int</span> capacity<span class="token punctuation">;</span>

<span class="token comment">//链表已有元素大小，使用 AtomicInteger，所以是线程安全的</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> AtomicInteger count <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">AtomicInteger</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">//链表头</span>
<span class="token keyword">transient</span> Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> head<span class="token punctuation">;</span>

<span class="token comment">//链表尾</span>
<span class="token keyword">private</span> <span class="token keyword">transient</span> Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> last<span class="token punctuation">;</span>
<span class="token comment">// 链表结构 end</span>

<span class="token comment">// 锁 begin</span>
<span class="token comment">//take 时的锁</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantLock takeLock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// take 的条件队列，condition 可以简单理解为基于 ASQ 同步机制建立的条件队列</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> Condition notEmpty <span class="token operator">=</span> takeLock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// put 时的锁，设计两把锁的目的，主要为了 take 和 put 可以同时进行</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> ReentrantLock putLock <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">ReentrantLock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// put 的条件队列</span>
<span class="token keyword">private</span> <span class="token keyword">final</span> Condition notFull <span class="token operator">=</span> putLock<span class="token punctuation">.</span><span class="token function">newCondition</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// 锁 end</span>

<span class="token comment">// 迭代器 </span>
<span class="token comment">// 实现了自己的迭代器</span>
<span class="token keyword">private</span> <span class="token keyword">class</span> <span class="token class-name">Itr</span> <span class="token keyword">implements</span> <span class="token class-name">Iterator</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> <span class="token punctuation">{</span>
………………
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从代码上来看，结构是非常清晰的，三种结构各司其职：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">链表的作用是为了保存当前节点，节点中的数据可以是任意东西，是一个泛型，比如说队列被应用到线程池时，节点就是线程，比如队列被应用到消息队列中，节点就是消息，节点的含义主要看队列被使用的场景；</li>
<li style="font-size: 20px; line-height: 38px;">锁有 take 锁和 put 锁，是为了保证队列操作时的线程安全，设计两种锁，是为了 take 和 put 两种操作可以同时进行，互不影响。</li>
</ol>
</div><div class="cl-preview-section"><h3 id="初始化">1.4 初始化</h3>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">初始化有三种方式：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">指定链表容量大小；</li>
<li style="font-size: 20px; line-height: 38px;">不指定链表容量大小，默认是 Integer 的最大值；</li>
<li style="font-size: 20px; line-height: 38px;">对已有集合数据进行初始化。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 不指定容量，默认 Integer 的最大值</span>
<span class="token keyword">public</span> <span class="token function">LinkedBlockingQueue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 指定链表容量大小，链表头尾相等，节点值（item）都是 null</span>
<span class="token keyword">public</span> <span class="token function">LinkedBlockingQueue</span><span class="token punctuation">(</span><span class="token keyword">int</span> capacity<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>capacity <span class="token operator">&lt;=</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalArgumentException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">this</span><span class="token punctuation">.</span>capacity <span class="token operator">=</span> capacity<span class="token punctuation">;</span>
    last <span class="token operator">=</span> head <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span><span class="token punctuation">(</span>null<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>

<span class="token comment">// 已有集合数据进行初始化</span>
<span class="token keyword">public</span> <span class="token function">LinkedBlockingQueue</span><span class="token punctuation">(</span>Collection<span class="token operator">&lt;</span><span class="token operator">?</span> <span class="token keyword">extends</span> <span class="token class-name">E</span><span class="token operator">&gt;</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token keyword">this</span><span class="token punctuation">(</span>Integer<span class="token punctuation">.</span>MAX_VALUE<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">final</span> ReentrantLock putLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>putLock<span class="token punctuation">;</span>
    putLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// Never contended, but necessary for visibility</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token keyword">int</span> n <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span>
        <span class="token keyword">for</span> <span class="token punctuation">(</span>E e <span class="token operator">:</span> c<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// 集合内的元素不能为空</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">==</span> null<span class="token punctuation">)</span>
                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token comment">// capacity 代表链表的大小，在这里是 Integer 的最大值</span>
            <span class="token comment">// 如果集合类的大小大于 Integer 的最大值，就会报错</span>
            <span class="token comment">// 其实这个判断完全可以放在 for 循环外面，这样可以减少 Integer 的最大值次循环(最坏情况)</span>
            <span class="token keyword">if</span> <span class="token punctuation">(</span>n <span class="token operator">==</span> capacity<span class="token punctuation">)</span>
                <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">IllegalStateException</span><span class="token punctuation">(</span><span class="token string">"Queue full"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token function">enqueue</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            <span class="token operator">++</span>n<span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        count<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span>n<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        putLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">对于初始化源码，我们说明两点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">初始化时，容量大小是不会影响性能的，只影响在后面的使用，因为初始化队列太小，容易导致没有放多少就会报队列已满的错误；</li>
<li style="font-size: 20px; line-height: 38px;">在对给定集合数据进行初始化时，源码给了一个不优雅的示范，我们不反对在每次 for 循环的时候，都去检查当前链表的大小是否超过容量，但我们希望在 for 循环开始之前就做一步这样的工作。举个列子，给定集合大小是 1 w，链表大小是 9k，按照现在代码实现，只能在 for 循环 9k 次时才能发现，原来给定集合的大小已经大于链表大小了，导致 9k 次循环都是在浪费资源，还不如在 for 循环之前就 check 一次，如果 1w &gt; 9k，直接报错即可。</li>
</ol>
</div><div class="cl-preview-section"><h2 id="阻塞新增" style="font-size: 30px;">2 阻塞新增</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">新增有多种方法，如：add、put、offer，三者的区别上文有说。我们拿 put 方法为例，put 方法在碰到队列满的时候，会一直阻塞下去，直到队列不满时，并且自己被唤醒时，才会继续去执行，源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 把e新增到队列的尾部。</span>
<span class="token comment">// 如果有可以新增的空间的话，直接新增成功，否则当前线程陷入等待</span>
<span class="token keyword">public</span> <span class="token keyword">void</span> <span class="token function">put</span><span class="token punctuation">(</span>E e<span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>
    <span class="token comment">// e 为空，抛出异常</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>e <span class="token operator">==</span> null<span class="token punctuation">)</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">NullPointerException</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token comment">// 预先设置 c 为 -1，约定负数为新增失败</span>
    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> node <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">Node</span><span class="token operator">&lt;</span>E<span class="token operator">&gt;</span><span class="token punctuation">(</span>e<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">final</span> ReentrantLock putLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>putLock<span class="token punctuation">;</span>
    <span class="token keyword">final</span> AtomicInteger count <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token punctuation">;</span>
    <span class="token comment">// 设置可中断锁</span>
    putLock<span class="token punctuation">.</span><span class="token function">lockInterruptibly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 队列满了</span>
        <span class="token comment">// 当前线程阻塞，等待其他线程的唤醒(其他线程 take 成功后就会唤醒此处被阻塞的线程)</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span>count<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> capacity<span class="token punctuation">)</span> <span class="token punctuation">{</span>
            <span class="token comment">// await 无限等待</span>
            notFull<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>

        <span class="token comment">// 队列没有满，直接新增到队列的尾部</span>
        <span class="token function">enqueue</span><span class="token punctuation">(</span>node<span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 新增计数赋值,注意这里 getAndIncrement 返回的是旧值</span>
        <span class="token comment">// 这里的 c 是比真实的 count 小 1 的</span>
        c <span class="token operator">=</span> count<span class="token punctuation">.</span><span class="token function">getAndIncrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

        <span class="token comment">// 如果链表现在的大小 小于链表的容量，说明队列未满</span>
        <span class="token comment">// 可以尝试唤醒一个 put 的等待线程</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">+</span> <span class="token number">1</span> <span class="token operator">&lt;</span> capacity<span class="token punctuation">)</span>
            notFull<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token comment">// 释放锁</span>
        putLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// c==0，代表队列里面有一个元素</span>
    <span class="token comment">// 会尝试唤醒一个take的等待线程</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token function">signalNotEmpty</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 入队，把新元素放到队尾</span>
<span class="token keyword">private</span> <span class="token keyword">void</span> <span class="token function">enqueue</span><span class="token punctuation">(</span>Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> node<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    last <span class="token operator">=</span> last<span class="token punctuation">.</span>next <span class="token operator">=</span> node<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">从源码中我们可以总结以下几点：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">往队列新增数据，第一步是上锁，所以新增数据是线程安全的；</li>
<li style="font-size: 20px; line-height: 38px;">队列新增数据，简单的追加到链表的尾部即可；</li>
<li style="font-size: 20px; line-height: 38px;">新增时，如果队列满了，当前线程是会被阻塞的，阻塞的底层使用是锁的能力，底层实现其它也和队列相关，原理我们在锁章节会说到；</li>
<li style="font-size: 20px; line-height: 38px;">新增数据成功后，在适当时机，会唤起 put 的等待线程（队列不满时），或者 take 的等待线程（队列不为空时），这样保证队列一旦满足 put 或者 take 条件时，立马就能唤起阻塞线程，继续运行，保证了唤起的时机不被浪费。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">以上就是 put 方法的原理，至于 offer 方法阻塞超过一端时间后，仍未成功，就会直接返回默认值的实现，和 put 方法相比只修改了几行代码，如下截图：</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;"><img class="" src="https://img.mukewang.com/5d9db53e000110c724481374.png" data-original="//img.mukewang.com/5d9db53e000110c724481374.png" alt="图片描述"></p>
</div><div class="cl-preview-section"><h2 id="阻塞删除" style="font-size: 30px;">3 阻塞删除</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">删除的方法也很多，我们主要看两个关键问题：</p>
</div><div class="cl-preview-section"><ol>
<li style="font-size: 20px; line-height: 38px;">删除的原理是怎样的；</li>
<li style="font-size: 20px; line-height: 38px;">查看并删除和只查看不删除两种的区别是如何实现的。</li>
</ol>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">首先我们来看第一个问题，我们以 take 方法为例，说明一下查看并删除的底层源码：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 阻塞拿数据</span>
<span class="token keyword">public</span> E <span class="token function">take</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">throws</span> InterruptedException <span class="token punctuation">{</span>
    E x<span class="token punctuation">;</span>
    <span class="token comment">// 默认负数，代表失败</span>
    <span class="token keyword">int</span> c <span class="token operator">=</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
    <span class="token comment">// count 代表当前链表数据的真实大小</span>
    <span class="token keyword">final</span> AtomicInteger count <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>count<span class="token punctuation">;</span>
    <span class="token keyword">final</span> ReentrantLock takeLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>takeLock<span class="token punctuation">;</span>
    takeLock<span class="token punctuation">.</span><span class="token function">lockInterruptibly</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 空队列时，阻塞，等待其他线程唤醒</span>
        <span class="token keyword">while</span> <span class="token punctuation">(</span>count<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
            notEmpty<span class="token punctuation">.</span><span class="token function">await</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
        <span class="token comment">// 非空队列，从队列的头部拿一个出来</span>
        x <span class="token operator">=</span> <span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token comment">// 减一计算，注意 getAndDecrement 返回的值是旧值</span>
        <span class="token comment">// c 比真实的 count 大1</span>
        c <span class="token operator">=</span> count<span class="token punctuation">.</span><span class="token function">getAndDecrement</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        
        <span class="token comment">// 如果队列里面有值，从 take 的等待线程里面唤醒一个。</span>
        <span class="token comment">// 意思是队列里面有值啦,唤醒之前被阻塞的线程</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">&gt;</span> <span class="token number">1</span><span class="token punctuation">)</span>
            notEmpty<span class="token punctuation">.</span><span class="token function">signal</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        <span class="token comment">// 释放锁</span>
        takeLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment">// 如果队列空闲还剩下一个，尝试从 put 的等待线程中唤醒一个</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>c <span class="token operator">==</span> capacity<span class="token punctuation">)</span>
        <span class="token function">signalNotFull</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">return</span> x<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span class="token comment">// 队头中取数据</span>
<span class="token keyword">private</span> E <span class="token function">dequeue</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> h <span class="token operator">=</span> head<span class="token punctuation">;</span>
    Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> first <span class="token operator">=</span> h<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
    h<span class="token punctuation">.</span>next <span class="token operator">=</span> h<span class="token punctuation">;</span> <span class="token comment">// help GC</span>
    head <span class="token operator">=</span> first<span class="token punctuation">;</span>
    E x <span class="token operator">=</span> first<span class="token punctuation">.</span>item<span class="token punctuation">;</span>
    first<span class="token punctuation">.</span>item <span class="token operator">=</span> null<span class="token punctuation">;</span><span class="token comment">// 头节点指向 null，删除</span>
    <span class="token keyword">return</span> x<span class="token punctuation">;</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">整体流程和 put 很相似，都是先上锁，然后从队列的头部拿出数据，如果队列为空，会一直阻塞到队列有值为止。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">而查看不删除元素更加简单，直接把队列头的数据拿出来即可，我们以 peek 为例，源码如下：</p>
</div><div class="cl-preview-section"><pre class="  language-java"><code class="prism  language-java"><span class="token comment">// 查看并不删除元素，如果队列为空，返回 null</span>
<span class="token keyword">public</span> E <span class="token function">peek</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
    <span class="token comment">// count 代表队列实际大小，队列为空，直接返回 null</span>
    <span class="token keyword">if</span> <span class="token punctuation">(</span>count<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">==</span> <span class="token number">0</span><span class="token punctuation">)</span>
        <span class="token keyword">return</span> null<span class="token punctuation">;</span>
    <span class="token keyword">final</span> ReentrantLock takeLock <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>takeLock<span class="token punctuation">;</span>
    takeLock<span class="token punctuation">.</span><span class="token function">lock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">try</span> <span class="token punctuation">{</span>
        <span class="token comment">// 拿到队列头</span>
        Node<span class="token operator">&lt;</span>E<span class="token operator">&gt;</span> first <span class="token operator">=</span> head<span class="token punctuation">.</span>next<span class="token punctuation">;</span>
        <span class="token comment">// 判断队列头是否为空，并返回</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>first <span class="token operator">==</span> null<span class="token punctuation">)</span>
            <span class="token keyword">return</span> null<span class="token punctuation">;</span>
        <span class="token keyword">else</span>
            <span class="token keyword">return</span> first<span class="token punctuation">.</span>item<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> <span class="token keyword">finally</span> <span class="token punctuation">{</span>
        takeLock<span class="token punctuation">.</span><span class="token function">unlock</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
</code></pre>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">可以看出，查看并删除，和查看不删除两者从队头拿数据的逻辑不太一致，从而导致一个会删除，一个不会删除队头数据。</p>
</div><div class="cl-preview-section"><h2 id="总结" style="font-size: 30px;">4 总结</h2>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">本文通过 LinkedBlockingQueue 的源码，来介绍了下链表队列，当队列满和空的场景下，新增和删除数据时，队列有啥变化。</p>
</div><div class="cl-preview-section"><p style="font-size: 20px; line-height: 38px;">队列本身就是一个阻塞工具，我们可以把这个工具应用到各种阻塞场景中，比如说队列应用到线程池，当线程池跑满时，我们把新的请求都放到阻塞队列中等待；队列应用到消息队列，当消费者处理能力有限时，我们可以把消息放到队列中等待，让消费者慢慢消费；每应用到一个新的场景中，都是一个新的技术工具，所以学好队列，用处很大。</p>
</div>}
                        </div>
                    </div>
                                            <!-- 买过的阅读 -->
                        <div class="art-next-prev clearfix">
                                                                                                <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/860">
                                                                    <div class="prev l clearfix">
                                        <div class="icon l">
                                            <i class="imv2-arrow3_l"></i>
                                        </div>
                                        <p>
                                            18 场景集合：并发 List、Map的应用场景
                                        </p>
                                    </div>
                                </a>
                                                                                                                            <!-- 已买且开放 或者可以试读 -->
                                    <a href="/read/47/article/862">
                                                                    <div class="next r clearfix">
                                        <p>
                                            20 SynchronousQueue 源码解析
                                        </p>
                                        <div class="icon r">
                                            <i class="imv2-arrow3_r"></i>
                                        </div>

                                    </div>
                                </a>
                                                    </div>
                                    </div>
                <div class="comments-con js-comments-con" id="coments_con">
                </div>

                
            </div>
            
            
            

        </div>
    </div>
</div>

<div class="modal modal-jiaQun-new hide" id="modal-jiaQun">
    <div class="inner" style="">
        <div class="modal-close js-close-jiaQun">
            <i class="imv2-close"></i>
        </div>
        <div class="content">
            <img src="https://img2.mukewang.com/5d634d40000119e505400602.jpg">
            <div class="right-info">
                <div class="title">
                    扫码加入慕课Java核心用户群
                </div>
                <div class="desc">
                                            <p class="mb6">验证信息：<span id="joincode">1909271435058473</span><span class="copy js-copy-joincode">复制</span></p>
                                        <p class="mb6">QQ讨论群号：906691736</p>
                                            <p>QQ群URL：<a href="https://jq.qq.com/?_wv=1027&amp;k=55RtSbJ" target="_blank">点击访问</a></p>
                                    </div>
            </div>
            <p class="tip">若遇到搜索不到QQ群或加群失败，请联系客服邮箱:kf@imooc.com</p>
        </div>
    </div>
</div>
 
<!-- 专栏介绍页专栏评价 -->

<!-- 专栏介绍页底部三条评价 -->

<!-- 专栏阅读页弹层目录和介绍页页面目录 -->

<!-- 专栏阅读页发布回复 -->

<!-- 专栏阅读页发布评论 -->

<!-- 专栏阅读页底部评论 -->

<!-- 专栏阅读 单个 评论 -->

<!-- 新增回复和展开三条以外回复 -->

<!-- 立即订阅的弹窗 -->












</div></body></html>